#define DEBUG 0
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>


#include <sys/time.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "snri_module.h"
#include "kademliamain.h"
#include <net.h>
#include <KadClog.h>

#define CORRECTNESS_TEST 0
#define OUT_PACKET 0
#define IN_PACKET 1

void kad_module_send(
  uint32_t to,
  uint16_t port,
  const unsigned char * data,
  const unsigned int size);

time_t get_dvn_time();

#define PRINT_INCOMING_PACKETS 0
#define PRINT_OUTGOING_PACKETS 0

//////////////////////////////////////////////
//
//  Globals (per node).  Need to be registered with DVN.
//
//////////////////////////////////////////////
struct {
  in_addr_t my_addr;
  int mysocket;
  void* kad_instance_ptr;
  int i_am_bootstrap_node;
  time_t join_time;
  time_t last_death_check;
} instance;

int sent_messages = 0;

void _snri_mod_init (_snricall_fpref_t snricall){

  //SNRI_INITIALIZE(snricall);
  
  // register the globals here
  snri_register_globals(2,  sizeof(instance), &instance,  sizeof(sent_messages), &sent_messages);

  /* initialize the callback for udp sends so that we receive them and
   * can put them out into dvn.  If this module is loaded multiple times, then
   * it's possible this will be set more than once, which is fine since it
   * will be the exact same function.
   */
  dvn_net_set_udp_send_callback(kad_module_send);

  /* initialize the callback for getting the current dvn time.  It may get set
   * on multiple loads of the module, but it will be the same function.
   */
  KadClog_set_time_func(get_dvn_time);

  //printf("I am initialized\n");
  //snri_log("I am initialized on snri\n");
}

void _snri_mod_uninit() {

}

void _snri_mod_destroy() {
  struct timeval current_time;
  char time_str[100];
  get_pretty_time(time_str, sizeof(time_str));
  printf("%s I am dead: %s\n", time_str, htoa(instance.my_addr));

  snri_gettime(&current_time);
  dvn_net_on_destroy(instance.kad_instance_ptr, current_time);
  //free(instance.kad_instance_ptr);

  //printf("  %i,  %i, %i\n", my_data.my_addr, my_data.in_packets, my_data.out_packets);
}

time_t get_dvn_time() {
  struct timeval t;
  snri_gettime(&t);
  return t.tv_sec;
}

void uint2addr(in_addr_t addr, char* out_addr)
{
  if(out_addr == NULL)
    return;

  sprintf(out_addr, "%i.%i.%i.%i",
	  0xff & ((addr)>>24),
	  0xff & ((addr)>>16),
	  0xff & ((addr)>>8),
	  0xff & ((addr))
	  );
}

void printPacket(in_addr_t remote_addr, int dir, int len, unsigned char* data)
{
  if(CORRECTNESS_TEST){
    //TODO: use local ip address
    //printAddr(my_data.my_addr);
    if(dir == OUT_PACKET){
      printf("->");
    }else{
      printf("<-");
    }
    printAddr(remote_addr);
    printf(": %i bytes\n", len);
  }
}

/*
void logPacket(in_addr_t remote_addr, int dir, char* type, int len, unsigned char* data)
{
  char str_myaddr[32];
  char str_rmaddr[32];
  char str_dir[8];
  char log_entry[1024];
  struct timeval current_time;

  snri_gettime(&current_time);
  uint2addr(my_data.my_addr, str_myaddr);
  uint2addr(remote_addr, str_rmaddr);
  if(dir == OUT_PACKET){
    sprintf(str_dir, "->");
  }else{
    sprintf(str_dir, "<-");
  }
  sprintf(log_entry, "[%i]%s%s%s, %s:%i bytes",
	  current_time.tv_sec,
	  str_myaddr,
	  str_dir,
	  str_rmaddr,
	  type,
	  len);
  //snri_log(log_entry);                                                                                           
  printf("%s\n", log_entry);
}
*/

//
// Emulates the UDP layer.  Sends a message over DVN instead of UDP
//
void kad_module_send(				
		     uint32_t to,			/* Remote address */
		     uint16_t port,                     /* Remote port */
		     const unsigned char * data,	/* Data to deliver */
		     const unsigned int size	        /* Size of data */
		     ){

#if PRINT_OUTGOING_PACKETS
  char strMyAddr[20];
  char strToAddr[20];
  char time_str[100];
  get_pretty_time(time_str, sizeof(time_str));
  uint2addr(instance.my_addr, strMyAddr);
  uint2addr(to, strToAddr);
  printf("%s, %s->%s:%d - %i bytes\n", time_str, strMyAddr, strToAddr, port, size);
#endif
    snri_datagram_send(
    instance.mysocket,
    (in_addr_t) to,
    port,
    size,
    (char *) data);

}

  //------------------------------------
  //
  // We received a message from the network.
  //        Call queueing function in net.c to add the packet to the queue.
  //
  //------------------------------------
void  kad_module_on_message(
			    int socket,
			    in_addr_t source, 
			    int source_port, 
			    unsigned int data_length, 
			    char * data
			    ) {

  struct timeval current_time;
  snri_gettime(&current_time);

#if PRINT_INCOMING_PACKETS
  char strMyAddr[20];
  char strFromAddr[20];
  char time_str[100];
  get_pretty_time(time_str, sizeof(time_str));
  uint2addr(instance.my_addr, strMyAddr);
  uint2addr(source, strFromAddr);
  printf("%s %s->%s:%d - %i bytes\n", time_str, strMyAddr, strFromAddr, source_port, data_length);
  //printf("%i <- %i: %i bytes.\n", instance.my_addr, source, data_length);
#endif
  // Queue the message in the node's state
  dvn_net_onrecv(
    instance.kad_instance_ptr,
    source,
    source_port,
    data_length,
    (unsigned char *) data,
    current_time);
  
  // Then process the queue
  //printf("kad_module: onrecv() complete.  Starting on_process()\n");
  dvn_net_on_process(instance.kad_instance_ptr, current_time);
}


static int should_i_die(double alive_time) {

  // This is a function of how long the node has been alive
  double a = .4;
  double b = 6.0;
  double death_prob = 100.0 * (1.0 - (a + ((log(alive_time) / log(10.0)) / b)));
  int randval = rand() % 100;
  return randval <= (int) death_prob ? 1 : 0;
}


/////////////////////////////////////////////
//
// The Kad node functions
//
//////////////////////////////////////////////

void kad_module_event_callback(
                                int timerid,
                                void* arg
				){
  int sleep_until = 0;
  struct timeval current_time;
  snri_gettime(&current_time);
  
  //Services the incoming queue
  //printf("time = %d, processing message queue for node %i\n", t.tv_sec, kmo->my_address.inet_addr);

  // We need to steal the on_process call
  sleep_until = dvn_net_on_process(instance.kad_instance_ptr, current_time);

  // Run the "Should I die" routine, but only if we've been on the network for a bit,
  // and only if we're over an hour since the last check
  int i_should_die = 0;
  double alive_time = (current_time.tv_sec - instance.join_time) / 60;
  /*if (instance.i_am_bootstrap_node == 0
      && current_time.tv_sec > (instance.last_death_check + 3600)) {
    i_should_die = should_i_die(alive_time);
    instance.last_death_check = current_time.tv_sec;
  }
  if (i_should_die) {
    snri_die();
    return;
  }
  */

  // performance TEST!!! Remove the bootstrap node after 1 hour
  /*if (instance.i_am_bootstrap_node == 1 && current_time.tv_sec > 3600) {
    snri_die();
    return;
  }
  */
  // Sets a timer to expire when the next event will happen, and adds some jitter
  snri_timer_create(sleep_until * 1000 + (rand() %  1000), &kad_module_event_callback, NULL);
  return;
}


#define KAD_PORT 4013
void _snri_mod_instantiate(int argc, char * argv[], in_addr_t bootstrap_address) {

  char myip[32];
  char myport[32];
  void *curNodeMem = NULL;
  in_addr_t my_addr = snri_getip();
  struct timeval current_time;
  int delay = rand() % 1000;// creates a delay of up to 1000ms for the current to check the callback
  char time_str[100];

  // Gets the current state of the node from the global
  instance.my_addr = my_addr;
  instance.mysocket = snri_datagram_socket(my_addr, KAD_PORT /*port*/, &kad_module_on_message);
  snri_gettime(&current_time);
  instance.join_time = current_time.tv_sec;
  instance.last_death_check = instance.join_time;
  instance.i_am_bootstrap_node = bootstrap_address == INADDR_NONE ? 1 : 0;

  
  ///////////////////////////////////////
  //
  // Call the init function of net.c which will call the one in kademliamain.c in turn
  //
  //////////////////////////////////////
  

  sprintf(myip,"%s",htoa(my_addr));
  sprintf(myport,"%d",KAD_PORT);
  get_pretty_time(time_str, sizeof(time_str));

  //printf("DVN is starting node %s\n", myip);

  //Add a new link for a CKademlia object that will be created
  if(bootstrap_address == INADDR_NONE)
  {
    printf("%s Starting node: %s with no bootstrap nodes\n", time_str, myip);
    dvn_net_init(myip, myport, 0, 0x0, 0x0, &curNodeMem, current_time);//We don't need a bootstrapping node
  }else{

    printf("%s Starting node: %s with bootstrap node: ", time_str, myip);
    printAddr(bootstrap_address);
    printf("\n");
    dvn_net_init(myip, myport, 1, bootstrap_address, KAD_PORT,&curNodeMem, current_time);//We specify the bootstrapping node
  }
  //the init function will create a virtual node for us.  We give it to DVN to track it for us
  instance.kad_instance_ptr = curNodeMem;

  //Initiates the first timer callback.
  //Other timers will be set from there
  snri_timer_create(delay, &kad_module_event_callback, NULL);
}


